Better Presets - #61
Conversation
- presets are now connected to srcSet descriptors - providing a descriptor in methods `FileInfoInterface::srcSet()` and `LinkGeneratorInterface::srcSet()` is now optional, the descriptor is automatically resolved from used preset - added configuration option `disable_signature_on_known_modifiers` - known modifier paths are now resolved during DI container build time
|
Caution Review failedThe pull request is closed. ℹ️ Recent review infoConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR replaces Value-wrapper codec APIs with direct string/array methods, introduces PresetConfig and Preset value objects, makes descriptor parameters optional with lazy resolution, and adds KnownModifiers-driven conditional signature creation/verification. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant LinkGen as LinkGenerator
participant Presets as PresetCollection
participant Descriptor as Descriptor
participant Signature as SignatureStrategy
participant Known as KnownModifiers
Client->>LinkGen: srcSet(info, descriptor?=null)
alt descriptor is null and modifiers is string
LinkGen->>Presets: get(presetAlias)
Presets-->>LinkGen: Preset(modifiers, descriptor?, default)
alt preset has descriptor and value provided
LinkGen->>Descriptor: validateModifierValue(value, default)
Descriptor-->>LinkGen: validated value
LinkGen->>Descriptor: expandModifier(modCollection, value)
Descriptor-->>LinkGen: modifier map
else
LinkGen->>Presets: use preset.modifiers
end
end
LinkGen->>Signature: createToken(path)
Signature->>Known: isKnown(path)
alt known
Known-->>Signature: true
Signature-->>LinkGen: null
else
Known-->>Signature: false
Signature-->>LinkGen: token
end
LinkGen-->>Client: SrcSet (with/without token)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
tests/ImageServer/LocalImageServerTest.phpt (1)
366-366:⚠️ Potential issue | 🟡 MinorTypo in test method name:
OnlSource→OnlySource.✏️ Proposed fix
- public function testImageResponseShouldBeReturnedIfOnlSourceFileExists(): void + public function testImageResponseShouldBeReturnedIfOnlySourceFileExists(): void🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/ImageServer/LocalImageServerTest.phpt` at line 366, Rename the test method testImageResponseShouldBeReturnedIfOnlSourceFileExists to testImageResponseShouldBeReturnedIfOnlySourceFileExists to fix the typo; update the method declaration and any references (e.g., in test suites or annotations) that call or reference testImageResponseShouldBeReturnedIfOnlSourceFileExists so they point to the corrected method name.tests/Responsive/Descriptor/ArgsFacadeTest.phpt (1)
11-11:⚠️ Potential issue | 🟡 MinorUnused import flagged by CI —
PresetValueis no longer used.The pipeline failure confirms
no_unused_importsviolations.PresetValueis imported but not referenced after the migration toexpandModifiers.Proposed fix
-use SixtyEightPublishers\ImageStorage\Modifier\Codec\Value\PresetValue;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Responsive/Descriptor/ArgsFacadeTest.phpt` at line 11, Remove the now-unused import PresetValue from the test file (the import statement referencing SixtyEightPublishers\ImageStorage\Modifier\Codec\Value\PresetValue) since it’s no longer referenced after migrating to expandModifiers; locate the import in ArgsFacadeTest (tests/Responsive/Descriptor/ArgsFacadeTest.phpt) and delete that use line (or consolidate imports) so the no_unused_imports CI violation is resolved.tests/Modifier/Facade/ModifierFacadeTest.phpt (1)
16-16:⚠️ Potential issue | 🟡 MinorUnused import:
PresetValueis no longer referenced in this test file.The pipeline's PHP CS Fixer failure confirms
no_unused_importsviolations. This import is a leftover from the olddecode(PresetValue)flow that was replaced byexpandModifiers.🧹 Proposed fix
-use SixtyEightPublishers\ImageStorage\Modifier\Codec\Value\PresetValue;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Modifier/Facade/ModifierFacadeTest.phpt` at line 16, Remove the unused import PresetValue from the top of the test (it is no longer referenced in ModifierFacadeTest after replacing decode(PresetValue) with expandModifiers); locate the use statement "use SixtyEightPublishers\ImageStorage\Modifier\Codec\Value\PresetValue;" in ModifierFacadeTest and delete that line so PHP CS Fixer no_unused_imports violations are resolved.
🧹 Nitpick comments (12)
src/Responsive/Descriptor/WDescriptor.php (1)
87-108: Duplicated validation logic withvalidateModifierValue.Lines 95–107 repeat the same numeric-check-and-in_array logic found in
validateModifierValue(lines 74–84). Consider delegating tovalidateModifierValue(or extracting a shared private helper) to keep validation consistent in one place.Proposed refactor
public function expandModifier( ModifierCollectionInterface $modifierCollection, mixed $value, ): array { $wAlias = $modifierCollection ->getByName(Width::class) ->getAlias(); - if (is_numeric($value) && in_array((int) $value, $this->widths, true)) { - return [ - $wAlias => (int) $value, - ]; - } - - throw new InvalidArgumentException( - message: sprintf( - 'Invalid preset value "%s" passed for descriptor %s', - var_export($value, true), - $this, - ), - ); + return [ + $wAlias => $this->validateModifierValue($value, null), + ]; }Note:
validateModifierValue($value, null)withnulldefault works because$valuehere is expected to be numeric already (nottrue). Iftruecan be passed in this context, adjust the second argument accordingly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Responsive/Descriptor/WDescriptor.php` around lines 87 - 108, expandModifier duplicates the numeric + in_array validation already implemented in validateModifierValue; remove the duplicated logic and delegate to that method (or call a new shared private helper) to ensure single-source validation. Specifically, inside WDescriptor::expandModifier call $this->validateModifierValue($value, null) (or the extracted helper) and use its result to build and return the array with the width alias (obtained via getByName(Width::class)->getAlias()); if validation fails let validateModifierValue throw the same InvalidArgumentException so expandModifier no longer repeats the numeric/in_array checks.tests/Modifier/Preset/PresetCollectionTest.phpt (1)
17-36: Tests properly updated for Preset objects.The test covers add/has/get and the error case. Consider adding assertions for
$descriptorand$defaultDescriptorValueproperties on the retrievedPresetto ensure the full object round-trips correctly through the collection.Example additional assertions
Assert::same(['w' => 15], $collection->get('a')->modifiers); + Assert::null($collection->get('a')->descriptor); + Assert::null($collection->get('a')->defaultDescriptorValue); Assert::same(['w' => 15, 'pd' => 2.0], $collection->get('b')->modifiers);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Modifier/Preset/PresetCollectionTest.phpt` around lines 17 - 36, Add assertions in testPresetsShouldBeAdded to verify that Preset objects are round-tripped with their descriptor and defaultDescriptorValue intact: after retrieving each Preset via PresetCollection::get('a') and ::get('b'), assert the Preset->descriptor equals the expected descriptor (null in the current diff) and Preset->defaultDescriptorValue equals the expected default (null in the current diff) so both properties are explicitly checked along with modifiers; update expected values if different from null.src/Modifier/Codec/Codec.php (1)
40-47: Duplicate default-assignment logic betweenmodifiersToPathandpathToModifiers.The assigner/separator default logic (fallback to
':'/','when empty) is duplicated in both methods (lines 40–44 here and lines 79–80 inpathToModifiers). Consider extracting a small private helper to keep this in one place.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modifier/Codec/Codec.php` around lines 40 - 47, The default assigner/separator fallback logic is duplicated in modifiersToPath and pathToModifiers; extract a private helper method (e.g., private function normalizeModifierDelimiters(array $config): array) that reads $this->config[Config::MODIFIER_ASSIGNER] and Config::MODIFIER_SEPARATOR, applies the empty() ? ':' / ',' defaults, asserts string types, and returns ['assigner'=>..., 'separator'=>...]; then replace the duplicated blocks in modifiersToPath and pathToModifiers to call this helper and use the returned assigner and separator variables.src/Security/KnownModifiers.php (1)
7-19: LGTM — minor naming note on$list.The implementation is correct:
array<string, true>as a hash-set with O(1)issetlookup is the right approach.listis permitted as a property name since PHP 7.0, as reserved keywords are allowed for property, constant, and method names of classes, interfaces, and traits, so there's no technical issue here. The property name$listis a bit generic though — a more descriptive name like$modifierPathswould better convey its contents.✏️ Proposed rename for clarity
- /** - * `@param` array<string, true> $list - */ - public function __construct( - public readonly array $list, - ) {} + /** + * `@param` array<string, true> $modifierPaths + */ + public function __construct( + public readonly array $modifierPaths, + ) {} public function isKnown(string $modifiers): bool { - return isset($this->list[$modifiers]); + return isset($this->modifierPaths[$modifiers]); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Security/KnownModifiers.php` around lines 7 - 19, Rename the generic property $list to a clearer name (e.g., $modifierPaths) in the KnownModifiers class: update the constructor parameter and promoted property, the public readonly property declaration, and all usages such as the isKnown(string $modifiers) method to reference the new name ( KnownModifiers::$modifierPaths and its constructor promotion ). Ensure the docblock stays accurate (array<string, true>) and run tests or static analysis to catch any remaining references to $list.tests/Modifier/Codec/RuntimeCachedCodecTest.phpt (1)
47-75: Test method names are inverted in order.
testStringValueShouldBeDecodedAndCached2(line 47) appears beforetestStringValueShouldBeDecodedAndCached(line 62). The2suffix conventionally implies a follow-up test, but here it comes first, which is confusing. Consider renaming them to reflect their actual purpose, e.g.testPathCanBeDecodedAndResultIsCachedandtestCachedResultIsReturnedWithoutCallingInnerCodecAgain.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Modifier/Codec/RuntimeCachedCodecTest.phpt` around lines 47 - 75, The test method names are out of logical order and unclear: rename the two methods (currently testStringValueShouldBeDecodedAndCached2 and testStringValueShouldBeDecodedAndCached) to clearer, purpose-revealing names such as testPathCanBeDecodedAndResultIsCached for the one asserting decoding+cache behavior and testCachedResultIsReturnedWithoutCallingInnerCodecAgain for the one asserting the inner codec is only called once; update both method declarations (and any references) accordingly so their names reflect intent and order.README.md (1)
90-99: LGTM — minor indentation inconsistency in the example.The new nested
modifiers:structure and thew/defaultWtop-level preset fields are clearly illustrated. One nit:my_preset'smodifierschildren use 2-space extra indentation (lines 93–94), whilemy_preset_2's use 4-space extra (line 97). Both are valid NEON, but aligning them aids readability.📝 Proposed consistency fix
presets: my_preset: modifiers: - w: 150 - ar: '2x1.5' + w: 150 + ar: '2x1.5' my_preset_2: modifiers: ar: 1x2 w: [300, 600, 900] defaultW: 600🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 90 - 99, The README example has inconsistent indentation for the presets: align the nested modifiers children consistently between my_preset and my_preset_2 (e.g., make the w and ar under my_preset use the same 4-space extra indentation style as my_preset_2); update the modifiers block under my_preset so the w and ar lines match the indentation of my_preset_2 (presets, my_preset, my_preset_2, modifiers, w, ar, defaultW).src/Modifier/Codec/PresetCodec.php (1)
62-63: Implicit array initialization of$modifierson line 63.
$modifiersis first used with$modifiers[] = $preset->modifierswithout prior declaration. While this is valid PHP that auto-creates the array, an explicit initialization improves readability.♻️ Optional: explicit initialization
$preset = $this->presetCollection->get(presetAlias: $presetAlias); - $modifiers[] = $preset->modifiers; + $modifiers = [$preset->modifiers];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modifier/Codec/PresetCodec.php` around lines 62 - 63, The code uses $modifiers[] = $preset->modifiers without prior declaration; explicitly initialize $modifiers as an empty array before its first use (e.g., add $modifiers = [] before the loop or before the line that calls $this->presetCollection->get) so the variable is declared clearly; update the function in PresetCodec (where $presetAlias is retrieved via $this->presetCollection->get) to declare $modifiers = [] prior to appending.src/LinkGenerator/LinkGenerator.php (2)
49-51:getModifiers()called twice on line 49.Consider caching the result in a local variable to avoid the redundant call and improve readability.
♻️ Optional: cache modifiers
- if (null === $pathInfo->getModifiers() || [] === $pathInfo->getModifiers()) { + $modifiers = $pathInfo->getModifiers(); + if (null === $modifiers || [] === $modifiers) { $pathInfo = $pathInfo->withModifiers(['original' => true]); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/LinkGenerator/LinkGenerator.php` around lines 49 - 51, The code calls $pathInfo->getModifiers() twice; cache its result in a local variable (e.g., $modifiers = $pathInfo->getModifiers()) and use that variable for the null/empty check and the subsequent logic so you only invoke getModifiers() once; if $modifiers is null or an empty array call $pathInfo = $pathInfo->withModifiers(['original' => true]) as before.
101-124: Duplicated assigner-fallback logic acrossLinkGeneratorandPresetCodec.Lines 107-108 mirror the same pattern in
PresetCodec::doExpand(lines 55-56). If the fallback logic changes, both locations need updating.Consider extracting the assigner resolution (with the empty-string fallback to
':') into a shared utility or Config method to keep it DRY. This is not urgent but worth tracking for a follow-up.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/LinkGenerator/LinkGenerator.php` around lines 101 - 124, The assigner-fallback logic duplicated in LinkGenerator::resolveDescriptor (using $this->config[Config::MODIFIER_ASSIGNER] with an empty-string fallback to ':') should be extracted into a single utility method (e.g., Config::getModifierAssigner() or a small helper like ModifierAssigner::resolve()) and then used from both LinkGenerator::resolveDescriptor and PresetCodec::doExpand; update resolveDescriptor to call the new method instead of repeating lines 107-108 and remove the duplicate logic from PresetCodec so both locations rely on the shared implementation.src/Bridge/Nette/DI/ImageStorageExtension.php (1)
619-636: Variable$modifierson line 628 shadows the outer$modifiersdeclared on line 584.Inside the
foreachloop,$modifiers = $preset->modifiersoverwrites the reference to the modifier instances array built on lines 584–593. While no code after the loop uses the outer$modifiers, this shadowing is a maintenance hazard.♻️ Rename the inner variable to avoid shadowing
- $modifiers = $preset->modifiers; + $presetModifiers = $preset->modifiers; foreach ($preset->descriptor->iterateModifiers($modifierCollection) as $mod) { $known[] = $codec->modifiersToPath(array_merge( - $modifiers, + $presetModifiers, $mod, )); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Bridge/Nette/DI/ImageStorageExtension.php` around lines 619 - 636, The inner variable $modifiers inside the foreach over $presets shadows the outer $modifiers declared earlier (lines ~584–593); rename the inner variable (e.g., to $presetModifiers or $baseModifiers) so it does not overwrite the outer one and update its use in the array_merge call inside the descriptor loop (the code that currently does $modifiers = $preset->modifiers and array_merge($modifiers, $mod) should use the new name), leaving the outer $modifiers untouched.tests/Modifier/Codec/PresetCodecTest.phpt (1)
23-180: Consider extracting repeated mock setup into a helper orsetUp.Every test method repeats the same four-mock + constructor boilerplate. A
setUp()method (or a private factory) would reduce duplication and make the individual tests focus on the scenario-specific expectations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Modifier/Codec/PresetCodecTest.phpt` around lines 23 - 180, Extract the repeated mock and constructor boilerplate by adding a setUp() in the test class that creates and assigns properties for the common mocks and SUT (e.g. $this->innerCodec, $this->config, $this->modifierCollection, $this->presetCollection and $this->presetCodec = new PresetCodec(...)); update each test to reuse those properties and only set scenario-specific expectations (e.g. calls to $this->config->shouldReceive(...), $this->presetCollection->shouldReceive(...), $this->innerCodec->shouldReceive(...)); alternatively implement a private factory method (e.g. createPresetCodec()) that returns the configured mocks and PresetCodec instance and have tests call that to remove duplication.src/Modifier/Codec/RuntimeCachedCodec.php (1)
32-37: Cache key formodifiersToPathis order-sensitive on array inputs.
json_encodeproduces different strings for['w' => 100, 'h' => 200]vs['h' => 200, 'w' => 100], leading to cache misses for semantically equivalent modifier sets. If callers consistently produce arrays in the same insertion order this is a non-issue, but worth being aware of.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modifier/Codec/RuntimeCachedCodec.php` around lines 32 - 37, The cache key for RuntimeCachedCodec::modifiersToPath is order-sensitive because it json_encodes arrays; normalize array inputs into a canonical, order-independent representation before encoding by recursively sorting associative array keys (leave scalar/string inputs as-is), then json_encode that normalized value for use as the $key when reading/writing $this->cache['modifiersToPath']; after normalization call $this->codec->modifiersToPath($value) as before.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (47)
README.mdsrc/Bridge/Nette/DI/Config/PresetConfig.phpsrc/Bridge/Nette/DI/Config/StorageConfig.phpsrc/Bridge/Nette/DI/ImageStorageExtension.phpsrc/Config/Config.phpsrc/FileInfo.phpsrc/FileInfoInterface.phpsrc/ImageServer/LocalImageServer.phpsrc/ImageStorage.phpsrc/LinkGenerator/LinkGenerator.phpsrc/LinkGenerator/LinkGeneratorInterface.phpsrc/Modifier/AbstractModifier.phpsrc/Modifier/Codec/Codec.phpsrc/Modifier/Codec/CodecInterface.phpsrc/Modifier/Codec/PresetCodec.phpsrc/Modifier/Codec/RuntimeCachedCodec.phpsrc/Modifier/Codec/Value/PresetValue.phpsrc/Modifier/Codec/Value/Value.phpsrc/Modifier/Codec/Value/ValueInterface.phpsrc/Modifier/Facade/ModifierFacade.phpsrc/Modifier/Facade/ModifierFacadeFactory.phpsrc/Modifier/Facade/ModifierFacadeInterface.phpsrc/Modifier/Preset/Preset.phpsrc/Modifier/Preset/PresetCollection.phpsrc/Modifier/Preset/PresetCollectionInterface.phpsrc/PathInfo.phpsrc/PathInfoInterface.phpsrc/Responsive/Descriptor/ArgsFacade.phpsrc/Responsive/Descriptor/DescriptorInterface.phpsrc/Responsive/Descriptor/WDescriptor.phpsrc/Responsive/Descriptor/XDescriptor.phpsrc/Responsive/SrcSetGenerator.phpsrc/Security/KnownModifiers.phpsrc/Security/SignatureStrategy.phpsrc/Security/SignatureStrategyInterface.phptests/Bridge/Nette/DI/ImageStorageExtensionTest.phptests/Bridge/Nette/DI/config/ImageStorage/config.withModifiersAndApplicatorsAndValidatorsAndPresets.neontests/ImageServer/LocalImageServerTest.phpttests/LinkGenerator/LinkGeneratorTest.phpttests/Modifier/Codec/CodecTest.phpttests/Modifier/Codec/PresetCodecTest.phpttests/Modifier/Codec/RuntimeCachedCodecTest.phpttests/Modifier/Facade/ModifierFacadeTest.phpttests/Modifier/Preset/PresetCollectionTest.phpttests/PathInfoTest.phpttests/Responsive/Descriptor/ArgsFacadeTest.phpttests/Security/SignatureStrategyTest.phpt
💤 Files with no reviewable changes (3)
- src/Modifier/Codec/Value/PresetValue.php
- src/Modifier/Codec/Value/Value.php
- src/Modifier/Codec/Value/ValueInterface.php
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Bridge/Nette/DI/ImageStorageExtension.php`:
- Around line 584-593: The assertion in the array_map closure that processes
$extConfig->modifiers is too strict: it checks is_subclass_of($entity,
Modifier\AbstractModifier::class) even though the closure returns
Modifier\ModifierInterface and callers may provide classes (e.g. TestModifier)
that implement Modifier\ModifierInterface without extending AbstractModifier;
update the assertion to check is_subclass_of($entity,
Modifier\ModifierInterface::class) or use instanceof/implements semantics so the
closure in this anonymous function (the Statement $modifier handler that builds
new $entity(...$params)) accepts any class implementing
Modifier\ModifierInterface instead of requiring AbstractModifier.
In `@src/ImageServer/LocalImageServer.php`:
- Line 151: The current assignment to $token casts the result of
$request->getQueryParameter($signatureParameterName) which can be
array|string|null, causing a PHP Notice and PHPStan error when an array is
returned; update the code that sets $token so it first checks the return type
from $request->getQueryParameter($signatureParameterName) (e.g., $value =
$request->getQueryParameter($signatureParameterName)), then if $value is an
array extract the intended element (for example use reset($value) or implode if
multiple are expected), if it's a string use it directly, and if null fall back
to '' — then cast or type-hint $token to string; reference
$request->getQueryParameter, $signatureParameterName and $token to locate and
change the assignment.
In `@src/Modifier/Codec/Codec.php`:
- Around line 28-34: The InvalidArgumentException thrown in modifiersToPath
incorrectly says "decode" which is misleading; update the exception message in
the modifiersToPath method to reflect encoding (e.g., state that a string was
provided but an array of modifiers is required) so it clearly describes the
expected type and that this is an encoding operation; modify the
InvalidArgumentException message text in the modifiersToPath function
accordingly.
In `@src/Modifier/Facade/ModifierFacade.php`:
- Line 17: Remove the dead import "use function is_array;" from
ModifierFacade.php: locate the ModifierFacade class (and the top-of-file
imports) and delete the unused import line; also run a quick grep for any
remaining is_array usage in that class or related methods (e.g., where the
switch changed to is_string($modifiers)) to ensure no other code relies on the
removed import before committing.
In `@src/Responsive/Descriptor/DescriptorInterface.php`:
- Around line 25-34: The docblock for expandModifier contains a duplicate
`@return` and is missing the `@param` for $value; update the PHPDoc above the method
expandModifier(ModifierCollectionInterface $modifierCollection, mixed $value):
array to replace the extra `@return` with a proper `@param` annotation for $value
(e.g., `@param` mixed $value) and keep the single `@return` array<string,
string|numeric|bool>, preserving the `@throws` InvalidArgumentException entry.
- Around line 14-23: The docblock for validateModifierValue documents only the
$value parameter but the method signature includes a second parameter $default;
update the PHPDoc for DescriptorInterface::validateModifierValue to add a `@param`
entry for $default (use the same type union used for $value, e.g. `@param`
string|numeric|bool $default) and ensure the `@return/`@throws lines remain
correct so the docblock matches the method signature.
In `@src/Responsive/Descriptor/WDescriptor.php`:
- Around line 62-85: WDescriptor currently assumes $this->widths has at least
one element and validateModifierValue accesses $this->widths[0], causing an
undefined-offset when constructed empty; fix by adding a guard in the
WDescriptor constructor to validate that the provided widths array is non-empty
(or throw InvalidArgumentException) so instances always have at least one
preset, and keep validateModifierValue as-is (or add a defensive check at the
start of validateModifierValue to throw if $this->widths is empty). Update the
constructor (WDescriptor::__construct) to validate and error early, referencing
the widths property and validateModifierValue method.
In `@tests/Modifier/Codec/CodecTest.phpt`:
- Around line 31-35: Update the error message thrown by the modifiersToPath
function so it uses "encode" (or "convert") instead of "decode": locate the
modifiersToPath implementation and change the InvalidArgumentException message
from "Can not decode value of type string, the value must be array<string,
string|numeric|bool>." to something like "Can not encode value of type string,
the value must be array<string, string|numeric|bool>." so the wording correctly
reflects that modifiersToPath encodes an array to a path.
---
Outside diff comments:
In `@tests/ImageServer/LocalImageServerTest.phpt`:
- Line 366: Rename the test method
testImageResponseShouldBeReturnedIfOnlSourceFileExists to
testImageResponseShouldBeReturnedIfOnlySourceFileExists to fix the typo; update
the method declaration and any references (e.g., in test suites or annotations)
that call or reference testImageResponseShouldBeReturnedIfOnlSourceFileExists so
they point to the corrected method name.
In `@tests/Modifier/Facade/ModifierFacadeTest.phpt`:
- Line 16: Remove the unused import PresetValue from the top of the test (it is
no longer referenced in ModifierFacadeTest after replacing decode(PresetValue)
with expandModifiers); locate the use statement "use
SixtyEightPublishers\ImageStorage\Modifier\Codec\Value\PresetValue;" in
ModifierFacadeTest and delete that line so PHP CS Fixer no_unused_imports
violations are resolved.
In `@tests/Responsive/Descriptor/ArgsFacadeTest.phpt`:
- Line 11: Remove the now-unused import PresetValue from the test file (the
import statement referencing
SixtyEightPublishers\ImageStorage\Modifier\Codec\Value\PresetValue) since it’s
no longer referenced after migrating to expandModifiers; locate the import in
ArgsFacadeTest (tests/Responsive/Descriptor/ArgsFacadeTest.phpt) and delete that
use line (or consolidate imports) so the no_unused_imports CI violation is
resolved.
---
Nitpick comments:
In `@README.md`:
- Around line 90-99: The README example has inconsistent indentation for the
presets: align the nested modifiers children consistently between my_preset and
my_preset_2 (e.g., make the w and ar under my_preset use the same 4-space extra
indentation style as my_preset_2); update the modifiers block under my_preset so
the w and ar lines match the indentation of my_preset_2 (presets, my_preset,
my_preset_2, modifiers, w, ar, defaultW).
In `@src/Bridge/Nette/DI/ImageStorageExtension.php`:
- Around line 619-636: The inner variable $modifiers inside the foreach over
$presets shadows the outer $modifiers declared earlier (lines ~584–593); rename
the inner variable (e.g., to $presetModifiers or $baseModifiers) so it does not
overwrite the outer one and update its use in the array_merge call inside the
descriptor loop (the code that currently does $modifiers = $preset->modifiers
and array_merge($modifiers, $mod) should use the new name), leaving the outer
$modifiers untouched.
In `@src/LinkGenerator/LinkGenerator.php`:
- Around line 49-51: The code calls $pathInfo->getModifiers() twice; cache its
result in a local variable (e.g., $modifiers = $pathInfo->getModifiers()) and
use that variable for the null/empty check and the subsequent logic so you only
invoke getModifiers() once; if $modifiers is null or an empty array call
$pathInfo = $pathInfo->withModifiers(['original' => true]) as before.
- Around line 101-124: The assigner-fallback logic duplicated in
LinkGenerator::resolveDescriptor (using $this->config[Config::MODIFIER_ASSIGNER]
with an empty-string fallback to ':') should be extracted into a single utility
method (e.g., Config::getModifierAssigner() or a small helper like
ModifierAssigner::resolve()) and then used from both
LinkGenerator::resolveDescriptor and PresetCodec::doExpand; update
resolveDescriptor to call the new method instead of repeating lines 107-108 and
remove the duplicate logic from PresetCodec so both locations rely on the shared
implementation.
In `@src/Modifier/Codec/Codec.php`:
- Around line 40-47: The default assigner/separator fallback logic is duplicated
in modifiersToPath and pathToModifiers; extract a private helper method (e.g.,
private function normalizeModifierDelimiters(array $config): array) that reads
$this->config[Config::MODIFIER_ASSIGNER] and Config::MODIFIER_SEPARATOR, applies
the empty() ? ':' / ',' defaults, asserts string types, and returns
['assigner'=>..., 'separator'=>...]; then replace the duplicated blocks in
modifiersToPath and pathToModifiers to call this helper and use the returned
assigner and separator variables.
In `@src/Modifier/Codec/PresetCodec.php`:
- Around line 62-63: The code uses $modifiers[] = $preset->modifiers without
prior declaration; explicitly initialize $modifiers as an empty array before its
first use (e.g., add $modifiers = [] before the loop or before the line that
calls $this->presetCollection->get) so the variable is declared clearly; update
the function in PresetCodec (where $presetAlias is retrieved via
$this->presetCollection->get) to declare $modifiers = [] prior to appending.
In `@src/Modifier/Codec/RuntimeCachedCodec.php`:
- Around line 32-37: The cache key for RuntimeCachedCodec::modifiersToPath is
order-sensitive because it json_encodes arrays; normalize array inputs into a
canonical, order-independent representation before encoding by recursively
sorting associative array keys (leave scalar/string inputs as-is), then
json_encode that normalized value for use as the $key when reading/writing
$this->cache['modifiersToPath']; after normalization call
$this->codec->modifiersToPath($value) as before.
In `@src/Responsive/Descriptor/WDescriptor.php`:
- Around line 87-108: expandModifier duplicates the numeric + in_array
validation already implemented in validateModifierValue; remove the duplicated
logic and delegate to that method (or call a new shared private helper) to
ensure single-source validation. Specifically, inside
WDescriptor::expandModifier call $this->validateModifierValue($value, null) (or
the extracted helper) and use its result to build and return the array with the
width alias (obtained via getByName(Width::class)->getAlias()); if validation
fails let validateModifierValue throw the same InvalidArgumentException so
expandModifier no longer repeats the numeric/in_array checks.
In `@src/Security/KnownModifiers.php`:
- Around line 7-19: Rename the generic property $list to a clearer name (e.g.,
$modifierPaths) in the KnownModifiers class: update the constructor parameter
and promoted property, the public readonly property declaration, and all usages
such as the isKnown(string $modifiers) method to reference the new name (
KnownModifiers::$modifierPaths and its constructor promotion ). Ensure the
docblock stays accurate (array<string, true>) and run tests or static analysis
to catch any remaining references to $list.
In `@tests/Modifier/Codec/PresetCodecTest.phpt`:
- Around line 23-180: Extract the repeated mock and constructor boilerplate by
adding a setUp() in the test class that creates and assigns properties for the
common mocks and SUT (e.g. $this->innerCodec, $this->config,
$this->modifierCollection, $this->presetCollection and $this->presetCodec = new
PresetCodec(...)); update each test to reuse those properties and only set
scenario-specific expectations (e.g. calls to $this->config->shouldReceive(...),
$this->presetCollection->shouldReceive(...),
$this->innerCodec->shouldReceive(...)); alternatively implement a private
factory method (e.g. createPresetCodec()) that returns the configured mocks and
PresetCodec instance and have tests call that to remove duplication.
In `@tests/Modifier/Codec/RuntimeCachedCodecTest.phpt`:
- Around line 47-75: The test method names are out of logical order and unclear:
rename the two methods (currently testStringValueShouldBeDecodedAndCached2 and
testStringValueShouldBeDecodedAndCached) to clearer, purpose-revealing names
such as testPathCanBeDecodedAndResultIsCached for the one asserting
decoding+cache behavior and
testCachedResultIsReturnedWithoutCallingInnerCodecAgain for the one asserting
the inner codec is only called once; update both method declarations (and any
references) accordingly so their names reflect intent and order.
In `@tests/Modifier/Preset/PresetCollectionTest.phpt`:
- Around line 17-36: Add assertions in testPresetsShouldBeAdded to verify that
Preset objects are round-tripped with their descriptor and
defaultDescriptorValue intact: after retrieving each Preset via
PresetCollection::get('a') and ::get('b'), assert the Preset->descriptor equals
the expected descriptor (null in the current diff) and
Preset->defaultDescriptorValue equals the expected default (null in the current
diff) so both properties are explicitly checked along with modifiers; update
expected values if different from null.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/ImageServer/LocalImageServer.php (1)
151-151:⚠️ Potential issue | 🟠 Major
#@phpstan-ignore-line`` suppresses the error but does not fix the runtime type hazard.The
array|string|nullreturn type ofgetQueryParameteris still in play:?? ''coalesces onlynull, so an array value (e.g.?_s[]=foo) passes straight to(string), emitting aNotice: Array to string conversionand producing the literal token"Array". This was flagged in a previous review; the@phpstan-ignore-lineannotation silences static analysis but leaves the runtime behaviour unchanged.🛡️ Proposed fix (from previous review)
- $token = (string) ($request->getQueryParameter($signatureParameterName) ?? ''); # `@phpstan-ignore-line` + $rawToken = $request->getQueryParameter($signatureParameterName); + $token = is_string($rawToken) ? $rawToken : '';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ImageServer/LocalImageServer.php` at line 151, The current assignment to $token in LocalImageServer.php blindly casts the result of $request->getQueryParameter($signatureParameterName) to string and suppresses static analysis; instead, handle array returns explicitly: retrieve $param = $request->getQueryParameter($signatureParameterName), check is_array($param) and pick a safe scalar (e.g. $param[0] ?? ''), otherwise use the scalar or null default, then cast to string and remove the `@phpstan-ignore-line`; update the code around the $token assignment in the LocalImageServer class/method to use this safe branching.
🧹 Nitpick comments (2)
tests/Modifier/Facade/ModifierFacadeTest.phpt (1)
169-183: Missing test for the newgetPresetCollection()accessor.
testModifierCollectionShouldBeReturnedandtestCodecShouldBeReturnedboth follow the same trivial pattern —getPresetCollection()is the only new public method on this facade without equivalent coverage.💡 Suggested test to add
public function testPresetCollectionShouldBeReturned(): void { $presetCollection = Mockery::mock(PresetCollectionInterface::class); $facade = $this->createModifierFacade(presetCollection: $presetCollection); Assert::same($presetCollection, $facade->getPresetCollection()); }Would you like me to open a new issue to track this?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Modifier/Facade/ModifierFacadeTest.phpt` around lines 169 - 183, Add a unit test for the new getPresetCollection() accessor: create a Mockery mock of PresetCollectionInterface, pass it into createModifierFacade via the presetCollection parameter, then assert the facade->getPresetCollection() returns the same mock (similar pattern to testModifierCollectionShouldBeReturned and testCodecShouldBeReturned). Ensure the test method is named testPresetCollectionShouldBeReturned and uses Assert::same to compare the returned value to the provided mock.src/Modifier/Facade/ModifierFacade.php (1)
104-107: Consider using$this->codecdirectly for consistency.Every other private-field access in this class is direct (
$this->modifierCollection,$this->config, etc.). Routing through the public getter is inconsistent inside afinalclass.♻️ Proposed refactor
- if (is_string($modifiers)) { - $codec = $this->getCodec(); - $modifiers = $codec->expandModifiers(value: $modifiers); - } + if (is_string($modifiers)) { + $modifiers = $this->codec->expandModifiers($modifiers); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modifier/Facade/ModifierFacade.php` around lines 104 - 107, Replace the indirect getter call with direct private-field access for consistency: inside the ModifierFacade method containing the snippet (in the final class ModifierFacade), change uses of $this->getCodec() to $this->codec so the block becomes $codec = $this->codec; (and keep the subsequent $codec->expandModifiers(...) logic unchanged); this aligns with other private-field accesses like $this->modifierCollection and $this->config while preserving behavior.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/ImageServer/LocalImageServer.phpsrc/Modifier/Facade/ModifierFacade.phptests/Bridge/Nette/DI/ImageStorageExtensionTest.phptests/Modifier/Facade/ModifierFacadeTest.phpttests/PathInfoTest.phpttests/Responsive/Descriptor/ArgsFacadeTest.phpt
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/ImageServer/LocalImageServer.php`:
- Line 151: The current assignment to $token in LocalImageServer.php blindly
casts the result of $request->getQueryParameter($signatureParameterName) to
string and suppresses static analysis; instead, handle array returns explicitly:
retrieve $param = $request->getQueryParameter($signatureParameterName), check
is_array($param) and pick a safe scalar (e.g. $param[0] ?? ''), otherwise use
the scalar or null default, then cast to string and remove the
`@phpstan-ignore-line`; update the code around the $token assignment in the
LocalImageServer class/method to use this safe branching.
---
Nitpick comments:
In `@src/Modifier/Facade/ModifierFacade.php`:
- Around line 104-107: Replace the indirect getter call with direct
private-field access for consistency: inside the ModifierFacade method
containing the snippet (in the final class ModifierFacade), change uses of
$this->getCodec() to $this->codec so the block becomes $codec = $this->codec;
(and keep the subsequent $codec->expandModifiers(...) logic unchanged); this
aligns with other private-field accesses like $this->modifierCollection and
$this->config while preserving behavior.
In `@tests/Modifier/Facade/ModifierFacadeTest.phpt`:
- Around line 169-183: Add a unit test for the new getPresetCollection()
accessor: create a Mockery mock of PresetCollectionInterface, pass it into
createModifierFacade via the presetCollection parameter, then assert the
facade->getPresetCollection() returns the same mock (similar pattern to
testModifierCollectionShouldBeReturned and testCodecShouldBeReturned). Ensure
the test method is named testPresetCollectionShouldBeReturned and uses
Assert::same to compare the returned value to the provided mock.
Summary by CodeRabbit
New Features
Documentation
Refactor